#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/types.h>
#include <linux/ide.h>


/* dev define */
#define DTSLED_CNT		    1
#define DTSLED_NAME     "dtsled"

#define LED_OFF 	0				/* led off */
#define LED_ON  	1				/* led on */

// driver io mem
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;


/* dev core struct */
struct core_dtsled_dev{
    dev_t dev_id;           /* dev id   */
    int major;              /* major id */
    int minor;              /* minor id */
    struct cdev cdev;       /* cdev     */
    struct class *class;
    struct device *device;
    struct device_node * node;
};

u32 reg_data[16] = {0};

static int   __init dtsled_init(void);
static void  __exit dtsled_exit(void);

static int dtsled_open(struct inode *inode, struct file *file);
static int dtsled_release(struct inode *inode, struct file *file);
static ssize_t dtsled_read(struct file *file, char *buf, size_t size, loff_t *offset);
static ssize_t dtsled_write(struct file *file, const char *buf, size_t size, loff_t *offset);

static int user_dtsled_init(void);
static int user_dtsled_exit(void);
void user_dtsled_switch(u8 status);

/* struct impl */
struct file_operations dtsled_fops = {
    .open = dtsled_open,
    .release = dtsled_release,
    .read = dtsled_read,
    .write = dtsled_write,
};
static struct core_dtsled_dev dtsled_dev;

/* module init and exit */
module_init(dtsled_init);
module_exit(dtsled_exit);

/* license and author */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huafv");

static int   __init dtsled_init(void) {
    int ret = 0;
    struct property *proper = NULL;
    const char *status_string = NULL;
    int i = 0;
    int len = 0;

    printk(KERN_INFO "DTSLED init\n");


    /* to register_dev */
    dtsled_dev.major = 0;
    dtsled_dev.minor = 0;

    if(dtsled_dev.major) {
        dtsled_dev.dev_id = MKDEV(dtsled_dev.major, 0);
        ret = register_chrdev_region(dtsled_dev.dev_id, DTSLED_CNT, DTSLED_NAME);
    }else {
        ret = alloc_chrdev_region(&dtsled_dev.dev_id, 0, DTSLED_CNT, DTSLED_NAME);
        dtsled_dev.major = MAJOR(dtsled_dev.dev_id);
        dtsled_dev.minor = MINOR(dtsled_dev.dev_id);
    }

    if(ret < 0) {
        printk(KERN_WARNING "DTSLED register failed\n");
    }
    printk(KERN_INFO "DTSLED major: %d\n", dtsled_dev.major);

    /* add chr dev */
    dtsled_dev.cdev.owner = THIS_MODULE;
    cdev_init(&dtsled_dev.cdev, &dtsled_fops);
    cdev_add(&dtsled_dev.cdev, dtsled_dev.dev_id, DTSLED_CNT);

    if(ret < 0) {
        unregister_chrdev_region(dtsled_dev.dev_id, DTSLED_CNT);
        printk(KERN_WARNING "DTSLED cdev add failed\n");
    }

    /* create dev node */
    dtsled_dev.class = class_create(THIS_MODULE, DTSLED_NAME);
    if(IS_ERR(dtsled_dev.class)) {
        printk(KERN_WARNING "DTSLED class create failed\n");
        cdev_del(&dtsled_dev.cdev);
        unregister_chrdev_region(dtsled_dev.dev_id, DTSLED_CNT);
    }
    printk(KERN_INFO "DTSLED class registered\n");

    /* create device */
    dtsled_dev.device = device_create(dtsled_dev.class, NULL, dtsled_dev.dev_id, NULL, DTSLED_NAME);
    if(IS_ERR(dtsled_dev.device)) {
        printk(KERN_WARNING "DTSLED device create failed\n");
        class_destroy(dtsled_dev.class);
        cdev_del(&dtsled_dev.cdev);
        unregister_chrdev_region(dtsled_dev.dev_id, DTSLED_CNT);
    }

    printk(KERN_INFO "DTSLED device create success\n");

    /* find node on device-tree */
    dtsled_dev.node = of_find_node_by_path("/huafvled");
    if(dtsled_dev.node == NULL) {
        printk(KERN_WARNING "DTSLED device-tree node find failed\n");
    }

    /* 2、get compatible context */
    proper = of_find_property(dtsled_dev.node, "compatible", NULL);
    if(proper == NULL) {
        printk(KERN_WARNING "DTSLED device-tree property does not exist\n");
    }else {
        printk(KERN_INFO "DTSLED device-tree property compatible = %s\r\n", (char*)proper->value);
    }

    /* get status context */

    len = of_property_read_string(dtsled_dev.node, "status", &status_string);
    if(len < 0) {
        printk(KERN_WARNING "DTSLED device-tree property status does not exist\n");
    }else {
        printk(KERN_INFO "DTSLED device-tree property status = %s\r\n", status_string);
    }

    /* core get reg context */
    len = of_property_read_u32_array(dtsled_dev.node, "reg", reg_data, 10);
    if(len < 0) {
        printk(KERN_WARNING "DTSLED device-tree property reg_data does not exist\n");
    }else {
        printk("DTSLED device-tree property reg_data = \r\n");

        for(i = 0; i < 10; i++)
        {
            printk("%#X ", reg_data[i]);
        }
        printk(KERN_INFO "\r\n");
    }

    /* led init */
    user_dtsled_init();

    return ret;
}

static void  __exit dtsled_exit(void) {
    printk(KERN_INFO "DTSLED exit\n");
    user_dtsled_exit();
    device_destroy(dtsled_dev.class, dtsled_dev.dev_id);
    class_destroy(dtsled_dev.class);
    cdev_del(&dtsled_dev.cdev);
    unregister_chrdev_region(dtsled_dev.dev_id, DTSLED_CNT);
}
static int dtsled_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "DTSLED open\n");
    file->private_data = &dtsled_dev;
    return 0;
}
static int dtsled_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "DTSLED release\n");
    //struct core_dtsled_dev *dtsled_dev = (struct core_dtsled_dev *)file->private_data;

    return 0;
}
static ssize_t dtsled_read(struct file *file, char *buf, size_t size, loff_t *offset) {
    printk(KERN_INFO "DTSLED read\n");
    //struct core_dtsled_dev *dtsled_dev = (struct core_dtsled_dev *)file->private_data;

    return 0;
}
static ssize_t dtsled_write(struct file *file, const char *buf, size_t size, loff_t *offset) {
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    printk(KERN_INFO "DTSLED write\n");
    //struct core_dtsled_dev *dtsled_dev = (struct core_dtsled_dev *)file->private_data;

    retvalue = copy_from_user(databuf, buf, size);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];		/* 获取状态值 */

    if(ledstat == LED_ON) {
        user_dtsled_switch(LED_ON);		/* 打开LED灯 */
    } else if(ledstat == LED_OFF) {
        user_dtsled_switch(LED_OFF);	/* 关闭LED灯 */
    }
    return 0;
}

/**
 * @brief dtsled init
 * @return
 */
static int user_dtsled_init(void) {
    // init led
    u32 val;

    /* 1、hw_reg to io_mem */
    /* 1、hw_reg to io_mem */
#if 0
    CCM_CCGR1 =         ioremap(reg_data[0], reg_data[1]);
    SW_MUX_GPIO1_IO03 = ioremap(reg_data[2], reg_data[3]);
    SW_PAD_GPIO1_IO03 = ioremap(reg_data[4], reg_data[5]);
    GPIO1_DR =          ioremap(reg_data[6], reg_data[7]);
    GPIO1_GDIR =        ioremap(reg_data[8], reg_data[9]);
#else
    CCM_CCGR1 =         of_iomap(dtsled_dev.node, 0);
    SW_MUX_GPIO1_IO03 = of_iomap(dtsled_dev.node, 1);
    SW_PAD_GPIO1_IO03 = of_iomap(dtsled_dev.node, 2);
    GPIO1_DR =          of_iomap(dtsled_dev.node, 3);
    GPIO1_GDIR =        of_iomap(dtsled_dev.node, 4);
#endif

    /* 2、enable GPIO1 clock */
    val = readl(CCM_CCGR1);
    val &= ~(3 << 26);	/* clear old setting */
    val |= (3 << 26);	/* set newval */
    writel(val, CCM_CCGR1);

    /* 3、set GPIO1_IO03 mux，mux set
     *    GPIO1_IO03，set io attr。
     */
    writel(5, SW_MUX_GPIO1_IO03);

    /*reg SW_PAD_GPIO1_IO03 set io attr
     *bit 16:0 HYS关闭
     *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
     */
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /* 4、set GPIO1_IO03 output */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3);	/* clear old setting */
    val |= (1 << 3);	/* set output */
    writel(val, GPIO1_GDIR);

    /* 5、default led off  */
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    return 0;
}

/**
 * @brief dtsled deinit
 * @return 0
 */
static int user_dtsled_exit(void) {
    printk(KERN_INFO "DTSLED IO exit\n");
    user_dtsled_switch(LED_OFF);
    iounmap(CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    return 0;
}

/**
 * @brief dtsled status switch
 * @param status led status in defines
 */
void user_dtsled_switch(u8 status)
{
    u32 val = 0;
    if(status == LED_ON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }else if(status == LED_OFF) {
        val = readl(GPIO1_DR);
        val|= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

// /home/huafv/linux/nfs/rootfs/lib/modules/4.1.15-g3dc0a4b/